<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>redis笔记 | SuJin博客</title><meta name="keywords" content="博客，SuJin"><meta name="author" content="SuJin"><meta name="copyright" content="SuJin"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="优势：redis 为什么这么快： 12345678910111.redis纯内存操作，读取非常快2.非阻塞io，IO多路复用  epoll3.redis单线程模型，减少了线程上下文切换(内存的 cpu依赖内存，内存到了再多的线程都没用)4.C语音编写，更亲和操作系统5.redis实现了自己的事件分离器6.丰富的数据结构-------redis数据结构----先说出数据结构---底层原理----使用">
<meta property="og:type" content="article">
<meta property="og:title" content="redis笔记">
<meta property="og:url" content="https://sujiesujie12456789.gitee.io/2021/12/18/redis/index.html">
<meta property="og:site_name" content="SuJin博客">
<meta property="og:description" content="优势：redis 为什么这么快： 12345678910111.redis纯内存操作，读取非常快2.非阻塞io，IO多路复用  epoll3.redis单线程模型，减少了线程上下文切换(内存的 cpu依赖内存，内存到了再多的线程都没用)4.C语音编写，更亲和操作系统5.redis实现了自己的事件分离器6.丰富的数据结构-------redis数据结构----先说出数据结构---底层原理----使用">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://picsum.photos/id/862/5451/3634">
<meta property="article:published_time" content="2021-12-18T12:47:07.000Z">
<meta property="article:modified_time" content="2022-03-30T13:12:17.689Z">
<meta property="article:author" content="SuJin">
<meta property="article:tag" content="博客，SuJin">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://picsum.photos/id/862/5451/3634"><link rel="shortcut icon" href="http://placekitten.com/350/200"><link rel="canonical" href="https://sujiesujie12456789.gitee.io/2021/12/18/redis/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'redis笔记',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-03-30 21:12:17'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="SuJin博客" type="application/atom+xml">
</head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="http://picsum.photos/id/859/1919/1919" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data is-center"><div class="data-item"><a href="/archives/"><div class="headline">文章</div><div class="length-num">11</div></a></div><div class="data-item"><a href="/tags/"><div class="headline">标签</div><div class="length-num">0</div></a></div><div class="data-item"><a href="/categories/"><div class="headline">分类</div><div class="length-num">0</div></a></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://picsum.photos/id/862/5451/3634')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">SuJin博客</a></span><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">redis笔记</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2021-12-18T12:47:07.000Z" title="发表于 2021-12-18 20:47:07">2021-12-18</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2022-03-30T13:12:17.689Z" title="更新于 2022-03-30 21:12:17">2022-03-30</time></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="redis笔记"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h2 id="优势："><a href="#优势：" class="headerlink" title="优势："></a>优势：</h2><p>redis 为什么这么快：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">1.redis纯内存操作，读取非常快</span><br><span class="line"></span><br><span class="line">2.非阻塞io，IO多路复用  epoll</span><br><span class="line"></span><br><span class="line">3.redis单线程模型，减少了线程上下文切换(内存的 cpu依赖内存，内存到了再多的线程都没用)</span><br><span class="line"></span><br><span class="line">4.C语音编写，更亲和操作系统</span><br><span class="line"></span><br><span class="line">5.redis实现了自己的事件分离器</span><br><span class="line"></span><br><span class="line">6.丰富的数据结构-------redis数据结构----先说出数据结构---底层原理----使用地方</span><br></pre></td></tr></table></figure>



<h2 id="1-redis底层数据结构"><a href="#1-redis底层数据结构" class="headerlink" title="1.redis底层数据结构"></a>1.redis底层数据结构</h2><p>==<strong>问：redis数据结构—-先说出数据结构—底层原理—-使用地方</strong>==</p>
<blockquote>
<p><strong>String、list、hash、set、zset</strong></p>
<p>数据结构：</p>
<p><strong>String：SDS简单动态字符串</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">动态字符串  c语言有字符串以<span class="string">&quot;\0&quot;</span>表示结束  <span class="string">&quot;dada\0&quot;</span> <span class="number">1.</span>不能存放二进制 <span class="number">2.</span>需要开辟空间</span><br><span class="line">redis---SDS 不以<span class="string">&quot;\0&quot;</span>结束 改变了c的字符串</span><br><span class="line">sdshdr&#123;</span><br><span class="line">	<span class="keyword">int</span> len:buf数组已经使用的数量 </span><br><span class="line">	<span class="keyword">int</span> free 未使用数量</span><br><span class="line">	<span class="keyword">char</span>[] buf;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>list：linkedList双端列表+zipList压缩列表</strong></p>
<p><strong>hash：zipList压缩列表+哈希表（HashTable）</strong></p>
<p>当列表中存储的数据量比较小的时候，列表就可以采用<strong>压缩列表</strong>的方式实现。具体需要同时满足下面两个条件：</p>
<ul>
<li><p>列表中保存的单个数据（有可能是字符串类型的）小于 64 字节；</p>
</li>
<li><p>列表中数据个数少于 512 个。</p>
<p>后续版本对列表数据结构进行了改造，使用 <strong>quicklist</strong> 代替了 ziplist 和 linkedlist。</p>
</li>
</ul>
<p><strong>Set：哈希表（HashTable）+ 整形数组（intset）</strong></p>
<p><strong>zset：zipList压缩列表+skipList跳表</strong></p>
</blockquote>
<p>==<strong>问：redis的优势(为什么这么快)，其它说了以后，redis丰富的数据类型与优秀数据结构</strong>==</p>
<blockquote>
<p><strong>1、优秀的底层数据结构</strong></p>
<p><strong>2、单线程模型：</strong>Redis 的单线程指的是 <strong>Redis 的键值对指令读写是由一个线程来执行的。</strong> 对于 Redis 的网络 IO （6.x 版本后网络 IO 使用多线程）、持久化、集群数据同步、异步删除等都是其他线程执行。</p>
<p>系统需要帮助它<strong>预先设置 CPU 寄存器和程序计数器</strong>，这称为 CPU 上下文。</p>
<p><strong>切换上下文时，我们需要完成一系列工作，这是非常消耗资源的操作。</strong></p>
<p>引入多线程开发，就需要使用同步原语来保护共享资源的并发读写，增加代码复杂度和调试难度。</p>
<p>3、<strong>Redis 采用 I/O 多路复用技术</strong>，并发处理连接。采用了 <strong>epoll</strong> + 自己实现的简单的事件框架。</p>
<p>epoll 中的读、写、关闭、连接都转化成了事件，然后利用 epoll 的多路复用特性，绝不在 IO 上浪费一点时间。</p>
<p><strong>4、redis全局哈希表。</strong></p>
</blockquote>
<blockquote>
<p>Redis 整体就是一个 哈希表来保存所有的键值对，无论数据类型是 5 种的任意一种。哈希表，本质就是一个数组，每个元素被叫做哈希桶，不管什么数据类型，每个桶里面的 entry 保存着实际具体值的指针。</p>
<p>而哈希表的**时间复杂度是 O(1)**，只需要计算每个键的哈希值，便知道对应的哈希桶位置，定位桶里面的 entry 找到对应数据，这个也是 Redis 快的原因之一。</p>
<img src="redis面试题.assets/image-20210830144751692.png" alt="image-20210830144751692" style="zoom:50%;" />
</blockquote>
<h2 id="2-Hash冲突怎么办"><a href="#2-Hash冲突怎么办" class="headerlink" title="==2.Hash冲突怎么办=="></a>==2.Hash冲突怎么办==</h2><blockquote>
<p>redis通过<strong>链式哈希</strong>（类似HashMap）解决哈希冲突。也就是一个桶里面的元素采用链表保存。</p>
<p>当满足下列条件时，进行哈希表的扩展：</p>
<p>（1）服务器还没执行BGSAVE或BGREWRITEAOF,且哈希表负载因子大于1</p>
<p>（2）服务器在执行BGSAVE或BGREWRITEAOF,且哈希表负载因此大于5</p>
<p>（3）负载因子 = 哈希值已保存节点数量 / 哈希表大小</p>
<p>（4）负载因子小于0.1时，程序开始对哈希表执行收缩操作</p>
<p>通过<strong>rehash</strong>可对哈希表扩展或收缩：采用了==<strong>渐进式 rehash</strong>==</p>
<p> 在渐进式 rehash执行期间，新添加到字典的键值对 一律会被保存到ht[1]里面，而ht[0]则不再进行任何添加操作，这一措施保证了ht[0]包含的键值对数量只减不增，并随着rehash操作的执行最终变成空表。</p>
</blockquote>
<p>string hash list set zset</p>
<p>value (redisObject)—-各种类型</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">redisdb对象  c对象 描述了一个数据库</span><br><span class="line">id  数据库编号0-15</span><br><span class="line">dict：字典  hashtable  set a abc </span><br><span class="line">expires：过期时间</span><br><span class="line">blocking_keys:阻塞的key</span><br><span class="line">watched_keys：监控的</span><br><span class="line"></span><br><span class="line">key----value指针指向数据类型(数据结构)</span><br><span class="line"></span><br><span class="line">typedef struct redisObject &#123;</span><br><span class="line">    // 类型（共有 5 种：string、list、hash、set、zset）</span><br><span class="line">    unsigned type:4;</span><br><span class="line">    // 编码（共有 8 种，两种 int （int、intset），两种 string（embstr、raw），一个压缩列表（ziplist），</span><br><span class="line">    //      三种数据结构（linkedlist、skipList、ht）</span><br><span class="line">    unsigned encoding:4;</span><br><span class="line">    </span><br><span class="line">    // 指向底层实现数据结构的指针</span><br><span class="line">    void *ptr;</span><br><span class="line">    </span><br><span class="line">    // 引用计数</span><br><span class="line">    int refcount;</span><br><span class="line">    </span><br><span class="line">    // 最后一次被命令程序访问</span><br><span class="line">    unsigned lru:22;  </span><br><span class="line">&#125; robj;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">redisDB[0]---字典：key---value(redisObject)</span><br><span class="line"></span><br><span class="line">动态字符串  c语言有字符串  </span><br><span class="line">&quot;dada\0&quot; 1.不能存放二进制 2.需要开辟空间</span><br><span class="line">redis---SDS &quot;\0&quot;结束 改变了c的字符串</span><br><span class="line">sdshdr&#123;</span><br><span class="line">	int len:buf数组已经使用的数量 </span><br><span class="line">	int free 未使用数量</span><br><span class="line">	char[] buf;</span><br><span class="line">&#125;</span><br><span class="line">buf长度 len+free+1</span><br><span class="line">好处:c:包含0x00（即：&#x27;\0&#x27;），刚好是字符串结束标志……</span><br><span class="line"></span><br><span class="line">sds解决c计算长度的复杂  减少了内存溢出  可以存放一些二进制等特殊字符</span><br><span class="line"></span><br><span class="line">sortset:跳跃表  空间换时间</span><br><span class="line"></span><br><span class="line">字典：hashtable</span><br><span class="line">cli-times33hash算法</span><br><span class="line">server：siphash算法</span><br><span class="line"></span><br><span class="line">压缩列表：ziplist  new int 数组 1</span><br><span class="line">1. 作为列表键的底层实现之一：当一个列表键只包含少量列表项，并且每个列表项要么就是小整数值，要么就是长度比较短的字符串，那么Redis就会使用压缩列表来做列表键的底层实现。</span><br><span class="line">2. 作为哈希键的底层实现之一：当一个哈希键只包含少量键值对，并且每个键值对的键和值要么就是小整数值，要么就是长度比较短的字符串，那么Redis就会使用压缩列表来做哈希键的底层实现</span><br><span class="line"></span><br><span class="line">String；sds</span><br><span class="line">hash:压缩列表（数据少），字典</span><br><span class="line">list:快速链表=双向链表+压缩列表</span><br><span class="line">set：intset与hashtable</span><br><span class="line">	set的底层存储intset和hashtable是存在编码转换的，使用intset存储必须满足下面两个条件，否则使用hashtable，条件如下：intset:只能存放int类型的set</span><br><span class="line">结合对象保存的所有元素都是整数值</span><br><span class="line">集合对象保存的元素数量不超过512个</span><br><span class="line"> hashtable的数据结构应该在前面的hash的章节已经介绍过了，所以这里着重讲一下intset这个新的数据结构好了。</span><br><span class="line">zset:压缩列表(少)跟跳表</span><br><span class="line"></span><br><span class="line">redis编码有10种</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h2 id="2-redis-GEO"><a href="#2-redis-GEO" class="headerlink" title="2.redis GEO"></a>2.redis GEO</h2><p>百度</p>
<p>附近的人：</p>
<p>获得用户ip—–调用百度地图/高德地图获取经纬度—-把经纬度存到redis</p>
<h2 id="3-redis事务-lua-管道"><a href="#3-redis事务-lua-管道" class="headerlink" title="3.redis事务/lua/管道"></a>3.redis事务/lua/管道</h2><blockquote>
<p><strong>redis事务：</strong>1、检查出有语法错误，才不会执行。2.命令错误(非整数类型去incr),—把所有正确的命令执行，并且返回所有信息</p>
<p>​    事务是把命令全部发送到服务器，管道是在客户端打包所有命令</p>
<p><strong>管道（pipeline）</strong>Pipeline 是<strong>非原子</strong>的，原生批量命令（mget、mset）是原子的，可以一次性发送多条命令并在执行完后一次性将结果返回，pipeline通过减少客户端与redis的通信次数来实现降低往返延时时间，而且Pipeline 实现的原理是队列，而队列的原理是时先进先出，这样就保证数据的顺序性。 Pipeline 的默认的同步的个数为53个。</p>
<p><strong>Lua脚本：</strong>一个命令需要上一个命令的返回结果，管道无法实现，所以用Lua（超卖问题）</p>
</blockquote>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">事务：做不了原子性,一些命令 按顺序执行----把多个命令打包发送 java---redis io操作减少  </span><br><span class="line">1.检查出语法错误，事务不会执行  2.命令错误(非整数类型去incr),---把所有正确的命令执行，并且返回所有信息</span><br><span class="line"></span><br><span class="line">lua：也是把命令打包，发送到redis一起 lua脚本  A B b命令可以依赖A命令返回值 if(get(a)&gt;0)decr a</span><br><span class="line"></span><br><span class="line">管道,也是把命令打包几个一起发到redis，上面两个不能被打断 </span><br><span class="line"></span><br><span class="line">A B两个客户端都发送的事务或者lua  里面的几条命令是一起执行 A执行完了才是B执行 或者是B 执行完了才是A</span><br><span class="line">pipline  AB 一起发管道  可以交替执行的</span><br><span class="line"></span><br><span class="line">开事务效率低：用处不大  lua保证超卖这些  pipline命令没有顺序要求 提高效率</span><br></pre></td></tr></table></figure>

<h2 id="4-redis穿透"><a href="#4-redis穿透" class="headerlink" title="4.redis穿透"></a>4.redis穿透</h2><p>—用的子类的缓存</p>
<p>假如黑客，一直换id攻击 ，redis的内存瞬间被穿透的key占满，布隆过滤器</p>
<p>新加一个商品  </p>
<p>id—放入bloomFilter—根据id查询的时候 判断是否存在—如果不存在 直接返回了—-如果存在 误判率万分之几—-又用代码（子类）</p>
<h2 id="5-redis持久化"><a href="#5-redis持久化" class="headerlink" title="5.redis持久化"></a>5.redis持久化</h2><blockquote>
<p>==<strong>redis持久化有两种RDB（默认）、AOF</strong>==</p>
<p>==<strong>RDB快照：</strong>==Redis在执行写操作时，内存数据会一直变化，内存快照，指的就是 Redis 内存中的数据在某一刻的完整状态数据。存储的是<strong>二进制文件</strong></p>
<p>快照文件叫做RDB文件。在数据恢复时，直接将RDB文件读入内存，完成恢复。</p>
<p>RDB有三种：</p>
<p>​    1、手动save：会阻塞当前Redis服务器，直到持久化完成，线上应该<strong>禁止使用</strong>。</p>
<p>​    2、手动调用<strong>bgsave-</strong>—后台持久化  会fork一个进程(不用创建进程，把进程复制一份，)，因此，<strong>阻塞只会发生在fork子进程的时候。</strong>fork了一个子进程，与主进程公用的内存数据空间。<strong>快照持久化完全交给子进程</strong>来处理，父进程继续处理客户端请求。先生成的是临时rbd，等生成完之后，再替换老的RDB文件。</p>
<p>​    3、<strong>自动触发：</strong></p>
<p>​        根据我们的 <strong>save m n</strong> 配置文件规则自动触发；save 300 10 表示300s内有10条写入，就产生快照</p>
<p>​        <strong>从节点全量复制时（第一次连接）</strong>，主节点发送rdb文件给从节点完成复制操作，主节点会触发 bgsave；<br>​                执行 debug reload 时；<br>​                执行 shutdown时，如果没有开启aof，也会触发。</p>
<img src="redis面试题.assets/image-20210831003118417.png" alt="image-20210831003118417" style="zoom:50%;" />

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"># 时间策略</span><br><span class="line">save <span class="number">900</span> <span class="number">1</span></span><br><span class="line">save <span class="number">300</span> <span class="number">10</span></span><br><span class="line">save <span class="number">60</span> <span class="number">10000</span></span><br><span class="line"># 文件名称</span><br><span class="line">dbfilename dump.rdb</span><br><span class="line"># 文件保存路径</span><br><span class="line">dir /home/work/app/redis/data/</span><br><span class="line"># 如果持久化出错，主进程是否停止写入</span><br><span class="line">stop-writes-on-bgsave-error yes</span><br><span class="line"># 是否压缩</span><br><span class="line">rdbcompression yes</span><br><span class="line"># 导入时是否检查</span><br><span class="line">rdbchecksum yes</span><br></pre></td></tr></table></figure>


</blockquote>
<blockquote>
<p>==<strong>AOF：</strong>==</p>
<p>Redis会把每一个<strong>写请求都记录</strong>在一个日志文件里。保存的是可识别文本</p>
<p><strong>流程：命令写入append=》追加到aof_buf缓冲区 =》sync同步到aof磁盘。</strong></p>
<p>需要==<strong>手动开启</strong>==：appendonly yes</p>
<p><strong>三种模式：</strong></p>
<p>1.每个<strong>写命令</strong>触发fsync保存到  appendfsync always</p>
<p>2.每秒触发fsync保存到磁盘：appendfsync everysec</p>
<p>3.依赖系统：appendfsync  no ；Linux操作系统，是<strong>每30秒</strong>进行一次fsync，将缓冲区中的数据写到磁盘上</p>
<p>三种都是调用fsync。由于在<strong>fsync时文件描述符会被阻塞，所以当前的写操作就会阻塞。</strong></p>
<p>AOF存的是写入的命令，太久了会有太多以前的无效数据，所以需要<strong>重写机制</strong>：<strong>copyonwrite</strong></p>
<p>==重写==<strong>手动触发：</strong> <code>bgrewriteaof</code>，</p>
<p><strong>自动触发</strong> ：</p>
<p>·auto-aof-rewrite-min-size：表示运行AOF重写时文件最小体积，默认 为64MB。</p>
<p>·auto-aof-rewrite-percentage：代表当前AOF文件空间 （aof_current_size）和上一次重写后AOF文件空间（aof_base_size）的比值。</p>
<p>减小文件大小，新的AOF文件中一条记录的操作只会有一次，而不像一份老文件那样，可能记录了对同一个值的多次操作。</p>
<p>—重写原理： fork进程 但是不会复制aof文件，<br>新的进程与原进程都会指向aof文件，</p>
<p>其生成过程和RDB类似，也是fork一个进程，直接遍历数据，写入新的AOF临时文件。在写入新文件的过程中，所有的写操作日志还是会写到原来老的 AOF文件中，同时还会<strong>记录在内存缓冲区</strong>中。当重完操作完成后，会将所有缓冲区中的日志一次性写入到临时文件中。然后调用原子性的rename命令用新的 AOF文件取代老的AOF文件。 </p>
<p>原进程在写怎么办？aof缓存区域  在备份的时候 写入的命令是放在缓存区域，备份完了把缓存的这部分命令写入原aof文件(阻塞，时间很短)。</p>
<img src="redis面试题.assets/image-20210831010047104.png" alt="image-20210831010047104" style="zoom:50%;" />

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"># 是否开启aof</span><br><span class="line">appendonly yes</span><br><span class="line"># 文件名称</span><br><span class="line">appendfilename <span class="string">&quot;appendonly.aof&quot;</span></span><br><span class="line"># 同步方式</span><br><span class="line">appendfsync everysec</span><br><span class="line"># aof重写期间是否同步</span><br><span class="line">no-appendfsync-on-rewrite no</span><br><span class="line"># 重写触发配置</span><br><span class="line">auto-aof-rewrite-percentage <span class="number">100</span></span><br><span class="line">auto-aof-rewrite-min-size 64mb</span><br><span class="line"># 加载aof时如果有错如何处理</span><br><span class="line">aof-load-truncated yes</span><br><span class="line"># 文件重写策略</span><br><span class="line">aof-rewrite-incremental-fsync yes</span><br></pre></td></tr></table></figure>


</blockquote>
<blockquote>
<p>==<strong>恢复数据：</strong>==</p>
<p><strong>先判断AOF文件，再判断RDB文件</strong></p>
<p>aof文件开头是rdb的格式（<strong>混合模式</strong>）， 先加载 rdb内容再加载剩余的 aof。<br>        aof文件开头不是rdb的格式，直接以aof格式加载整个文件。</p>
<p>优点：既能快速备份又能避免大量数据丢失<br>        缺点：RDB是压缩格式，AOF在读取它时可读性教差</p>
<img src="redis面试题.assets/image-20210831010213121.png" alt="image-20210831010213121" style="zoom: 33%;" />
</blockquote>
<blockquote>
<p>==<strong>优缺点：</strong>==</p>
<img src="redis面试题.assets/image-20210831011741018.png" alt="image-20210831011741018" style="zoom:25%;" />

<p>RDB：优点：存储的是紧凑压缩的二进制文件，适合备份。</p>
<p>​                加载RDB文件恢复数据快。</p>
<p>​            缺点：无法实时持久化；全量数据存储，耗时；fork进程耗内存。</p>
<p>AOF：优点：丢失数据小</p>
<p>​            缺点：文件大，恢复速度慢</p>
<p>==<strong>两者混用</strong>==（redis4.x）<strong>配置aof-use-rdb-preamble</strong></p>
<p>aof还是aof保存的可以是一部分rdb<br>        aof<strong>头部就存rdb文件格式</strong>，一部分是aof文件</p>
<p>1 子进程会把内存中的数据以RDB的方式写入aof中，<br>        2 把（<strong>自持久化开始到持久化结束</strong>）重写缓冲区中的<strong>增量命令</strong>以AOF方式写入到文件<br>        3 将含有RDB个数和AOF格数的AOF数据覆盖旧的AOF文件<br>新的AOF文件中，<strong>头部数据来自RDB文件，其余来自Redis持久化运行过程时的增量数据</strong></p>
</blockquote>
<p>redis的key都是基于内存，断电，</p>
<p>容灾：1.断电 磁盘在 只需要把数据放入磁盘</p>
<p>​            2.机器直接凉了:异地备份</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">RDB:快照  保存的是二进制,恢复数据快，直接  a=abc  可读性差  丢失的key概率大</span><br><span class="line">1.手动调用save  要求redis给我持久化，内存中的数据保存一份到磁盘 阻塞redis 跟redis操作用的同一个进程的同一个线程  阻塞--保证一致</span><br><span class="line">2.手动调用bgsave----后台持久化  fork一个进程(不用创建进程，把进程复制一份，)</span><br><span class="line">fork了一个进程，这个进程与主进程公用的内存数据空间，但是如果主进程进行写入，copyonwrite写时复制，生成临时文件---主进程在fork进程持久化期间进行的写入都放在临时文件里面，临时文件更新主文件(服务器卡顿)15 16</span><br><span class="line">java也是可以调用</span><br><span class="line">3.系统自动：需要配置</span><br><span class="line">save 900 1   如果900秒内,有1条变化,则产生快照</span><br><span class="line">  save 300 1000  如果300秒内有1000次变化,则产生快照</span><br><span class="line">  save 60 10000  如果60秒内有10000次变化,则产生快照</span><br><span class="line">15分钟一次</span><br><span class="line"></span><br><span class="line">stop-writes-on-bgsave-error yes  后台备份进程出错时,主进程停不停止写入?</span><br><span class="line"></span><br><span class="line">rdbcompression yes 导出的rdb文件是否压缩</span><br><span class="line"></span><br><span class="line">Rdbchecksum yes 导入rbd恢复时数据(读取rdb放入内存)时,要不要检验rdb的完整性</span><br><span class="line"></span><br><span class="line">dbfilename dump.rdb 生成的rdb文件名</span><br><span class="line"></span><br><span class="line">dir ./        rdb文件的放置路径</span><br><span class="line"></span><br><span class="line">save “”       在那三个save下面加save “”表示禁用rdb</span><br></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">AOF:rdb间隔比较长，丢失key的概率也大  记录的是命令 set a abc  a=abc  rdb比aof小，</span><br><span class="line">set a abc set b bbc flushall 只是aof 系统恢复的时候 会读取aof来恢复数据</span><br><span class="line">1.每个命令触发aof保存</span><br><span class="line"></span><br><span class="line">2.每秒触发aof保存</span><br><span class="line"></span><br><span class="line">3.依赖系统</span><br><span class="line"></span><br><span class="line"> **appendfsync no:**</span><br><span class="line"></span><br><span class="line">   当设置appendfsync为no的时候，Redis不会主动调用fsync去将AOF日志内容同步到磁盘，所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统，是每30秒进行一次fsync，将缓冲区中的数据写到磁盘上。</span><br><span class="line"></span><br><span class="line"> **appendfsync everysec**</span><br><span class="line"></span><br><span class="line">​    当设置appendfsync为everysec的时候，Redis会默认每隔一秒进行一次fsync调用，将缓冲区中的数据写到磁盘。但是当这一次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略，再等一秒钟。也就是在两秒后再进行fsync，这一次的fsync就不管会执行多 长时间都会进行。这时候由于在fsync时文件描述符会被阻塞，所以当前的写操作就会阻塞。</span><br><span class="line"></span><br><span class="line">​    结论就是，在绝大多数情况下，Redis会每隔一秒进行一 次fsync。在最坏的情况下，两秒钟会进行一次fsync操作。这一操作在大多数数据库系统中被称为group commit，就是组合多次写操作的数据，一次性将日志写到磁盘。</span><br><span class="line"></span><br><span class="line"> **appendfsync always**</span><br><span class="line"></span><br><span class="line"> 设置appendfsync为always时，每一次写操作都会调用一次fsync，这时数据是最安全的，当然，由于每次都会执行fsync，</span><br><span class="line"></span><br><span class="line">所以其性能也会受到影响</span><br><span class="line"></span><br><span class="line">AOF重写----命令---AOF文件越来越大----冗余命令  set a abc  set a bbc  list add a list add  b</span><br><span class="line">\1.   手动命令BGREWRITEAOF</span><br><span class="line"></span><br><span class="line">\2.   配置自动调用，如下:默认到达多少大小  或者大小到原来的一倍</span><br><span class="line">减小文件大小，---重写原理 fork进程 但是不会复制aof文件，</span><br><span class="line">新的进程与原进程都会指向aof文件，原进程在写怎么办？aof缓存区域  在备份的时候 写入的命令是放在缓存区域，备份完了把缓存的这部分命令写入原aof文件(阻塞，时间很短)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">aof丢失数据少，文件越来越大，redis4.x过后就可以两个混用，</span><br><span class="line">aof还是aof保存的可以是一部分rdb</span><br><span class="line">aof头部就存rdb文件格式，一部分是aof文件</span><br><span class="line"></span><br><span class="line">aof-use-rdb-preamble就可以开启两者混合持久化，取长补短</span><br></pre></td></tr></table></figure>

<h2 id="6-redis读写分离"><a href="#6-redis读写分离" class="headerlink" title="6.redis读写分离"></a>6.redis读写分离</h2><blockquote>
<p>Redis 提供了主从模式，通过主从复制，将数据冗余一份复制到其他 Redis 服务器。高并发、高可用（哨兵选举机制）</p>
<p>为了保证副本数据的一致性，<strong>主从架构采用了读写分离的方式</strong>。</p>
<ul>
<li><strong>读操作：主、从库都可以执行；</strong></li>
<li><strong>写操作：主库先执行，之后将写操作同步到从库；</strong></li>
</ul>
<p>==<strong>主从复制是如何实现的：</strong>==</p>
<p>同步分为三种情况：</p>
<ol>
<li>第一次主从库==全量复制==；</li>
<li>主从正常运行期间的同步（==增量==）；</li>
<li>主从库间网络断开重连同步（==增量==）。</li>
</ol>
<p>==<strong>面试官：主从复制还有其他作用么？</strong>==</p>
<ol>
<li>故障恢复：当主节点宕机，其他节点依然可以提供服务；</li>
<li>负载均衡：Master 节点提供写服务，Slave 节点提供读服务，分担压力；</li>
<li>高可用基石：是哨兵和 cluster 实施的基础，是高可用的基石。</li>
</ol>
<p><strong>==第一次同步怎么实现？==</strong></p>
<p>​    第一次复制过程：<strong>在建立连接前（准备阶段）、主库同步到从库（全量复制）、发送同步期间新写命令到从库</strong></p>
<p>从 Redis 2.8 开始，网络断了之后，主从库会采用增量复制的方式继续同步。</p>
<p><strong>==那完成全量同步后，正常运行过程中如何同步数据呢？==</strong></p>
<p>​    当主从库完成了全量复制，它们之间就会一直维护一个网络连接，主库会通过这个连接将后续陆续收到的命令操作再同步给从库，这个过程也称为==基于长连接的命令传播==，使用长连接的目的就是避免频繁建立连接导致的开销。</p>
<p>增量复制：<strong>用于网络中断等情况后的复制，只将中断期间主节点执行的写命令发送给从节点，与全量复制相比更加高效</strong>。</p>
<p>==<strong>主从库间的网络断了咋办？断开后要重新全量复制么？</strong>==</p>
<p>从Redis2.8之后，使用增量复制。断开重连增量复制的实现奥秘就是 ==<code>repl_backlog_buffer</code>== 缓冲区，不管在什么时候 master 都会将写指令操作记录在 <code>repl_backlog_buffer</code> 中，因为内存有限， <code>repl_backlog_buffer</code> 是一个<strong>定长的环形数组</strong>，<strong>如果数组内容满了，就会从头开始覆盖前面的内容</strong>。</p>
<p>master 使用 <code>master_repl_offset</code>记录自己写到的位置偏移量，slave 则使用 <code>slave_repl_offset</code>记录已经读取到的偏移量。</p>
</blockquote>
<p>cp ap</p>
<p>1.在生产中难以避免单台redis出现故障，保证<strong>高可用</strong>可以用主从，选举模式</p>
<p>2.单台redis官网说能抗住10w并发量，超过了10w，<strong>高并发</strong>，就可以<strong>主从</strong>：主服务器抗写压力，从服务器抗读</p>
<p>3.QPS瓶颈</p>
<p>​     Qps：每秒处理的查询次数</p>
<p>​     TPS：每秒处理的事务数(从请求到获得数据，称为一个完整事务过程)</p>
<p>​     大家记住第一个单词，Q就是query缩写，T就是transaction缩写</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">Redis全量复制一般发生在Slave初始化阶段，这时Slave需要将Master上的所有数据都复制一份。具体步骤如下： </span><br><span class="line"></span><br><span class="line">　　1）从服务器连接主服务器，发送SYNC命令； </span><br><span class="line"></span><br><span class="line">　　2）主服务器接收到SYNC命名后，开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令； </span><br><span class="line">　　</span><br><span class="line">　　	主服务器有空间---复制  没有空间---socket</span><br><span class="line"></span><br><span class="line">　　3）主服务器BGSAVE执行完后，向所有从服务器发送快照文件，并在发送期间继续记录被执行的写命令； </span><br><span class="line"></span><br><span class="line">　　4）从服务器收到快照文件后丢弃所有旧数据，载入收到的快照； </span><br><span class="line"></span><br><span class="line">　　5）主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令； </span><br><span class="line"></span><br><span class="line">　　6）从服务器完成对快照的载入，开始接收命令请求，并执行来自主服务器缓冲区的写命令；</span><br><span class="line"></span><br><span class="line"> 增量同步</span><br><span class="line">　　Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 </span><br><span class="line"></span><br><span class="line">增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令，从服务器接收并执行收到的写命令。</span><br><span class="line"></span><br><span class="line">Redis主从同步策略</span><br><span class="line">　　主从刚刚连接的时候，进行全量同步；全同步结束后，进行增量同步。当然，如果有需要，slave 在任何时候都可以发起全量同步。redis 策略是，无论如何，首先会尝试进行增量同步，如不成功，要求从机进行全量同步。</span><br><span class="line"> 注意点</span><br><span class="line">如果多个Slave断线了，需要重启的时候，因为只要Slave启动，就会发送sync请求和主机全量同步，当多个同时出现的时候，可能会导致Master IO剧增宕机。</span><br></pre></td></tr></table></figure>

<h2 id="7-redis哨兵机制-★★★★★-sentinel"><a href="#7-redis哨兵机制-★★★★★-sentinel" class="headerlink" title="7.redis哨兵机制(★★★★★)sentinel"></a>7.redis哨兵机制(★★★★★)sentinel</h2><blockquote>
<p>哨兵是 Redis 的一种运行模式，它专注于对 Redis 实例（主节点、从节点）运行状态的监控，并能够在主节点发生故障时通过一系列的机制<strong>实现选主及主从切换，实现故障转移</strong>，确保整个 Redis 系统的可用性。</p>
<p>==<strong>有三个定时任务：</strong>==<strong>1、2、10</strong></p>
<p>​    ==<strong>每10秒每个sentinel对master发送info：</strong>==详细信息，master收到消息之后，将slave返回给sentinel，sentinel收到后，再与每个slave建立连接。并根据这个连接持续监控slave。</p>
<p>​    ==<strong>每2秒各个sentinel会交换对节点的看法以及sentinel自身的信息。</strong>==master 有一个 <code>__sentinel__:hello</code> 的专用通道，用于哨兵之间发布和订阅消息。每个sentinel订阅了该频道，会通过该频道发送对节点的看法以及sentinel自身的信息，也能收到其他sentinel发送的信息。</p>
<p>​    ==<strong>每1秒sentinel对其他sentinel以及所有主从节点发送ping</strong>==，==心跳检测==，作为正常或宕机的判断依据</p>
<p>==<strong>什么是主客观下线：</strong>==</p>
<p>​        <strong>主观下线：</strong>在每秒的ping时，30秒（自己配置的）都没有回复，那么sentinel就会认为该节点失败</p>
<p>​        <strong>客观下线：</strong>所有sentinel中有2个（自己配置的2）都认为该redis节点失败(主观下线)，那么就认为真的失败了，就可以继续后续操作了。</p>
<p>原理就是发送sentinel is-master-down-by-addr(地址)到_sentinel:hello，其它订阅的就可以获得信息</p>
<p>==<strong>选举者机制</strong>==</p>
<p><strong>目的：</strong>选出一个sentinel领导，只要一个sentinel进行故障迁移操作</p>
<p>原理就是发送sentinel is-master-down-by-addr(地址)到_sentinel:hello，其它订阅的就可以获得信息</p>
<p>==<strong>故障迁移</strong>==</p>
<pre><code>    1.会查看slave中是否配置的优先级slave-priority，选择优先级高的(运维人员可以根据机器的配置高低去配置优先级)
    2.如果都没有配置那么就是相同的，会选择偏移量最大的slave节点(了解即可)
    3.选择runid最小的（就是redis运行的id，相当于一个标识符）
        
2、选择了新的master，sentinel就会对剩下的slave发送命令，去复制新的master，复制规则，如图，下面就是表示一个一个来
        
3、sentinel还会继续监控挂掉的master，如果master复活了，会命令master去从属于新选举的master
</code></pre>
<p>==<strong>脑裂问题</strong>==</p>
<blockquote>
<p>redis的集群脑裂是指因为网络问题，导致redis master节点跟redis slave节点和sentinel集群处于不同的网络分区，此时因为sentinel集群无法感知到master的存在，所以将slave节点提升为master节点。此时存在两个不同的master节点，就像一个大脑分裂成了两个。</p>
</blockquote>
<p><strong>解决方案</strong>：redis的配置文件中，存在两个参数</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">min-replicas-to-write 3</span><br><span class="line">min-replicas-max-lag 10</span><br></pre></td></tr></table></figure>

<p>redis中的异步复制情况下的数据丢失问题也能使用这两个参数</p>
</blockquote>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">### 17.3sentinel三个定时任务</span><br><span class="line"></span><br><span class="line">1.每10秒每个sentinel对master与slave节点执行info：详细信息</span><br><span class="line"></span><br><span class="line"> 通过info能发现master的slave节点</span><br><span class="line"></span><br><span class="line"> 确定主从关系(因为之前演示中可以看出主从会发生改变的)</span><br><span class="line"></span><br><span class="line">2.每2秒各个sentinel会交换对节点的看法以及sentinel自身的信息</span><br><span class="line"></span><br><span class="line"> 原理:在master中会有一个频道_sentinel:hello   redis发布订阅</span><br><span class="line"></span><br><span class="line">​    每个sentinel都订阅了该频道，就会通过该频道发送对节点的看法以及sentinel自身的信息，也能搜到其他sentinel发送的信息</span><br><span class="line"></span><br><span class="line">3.每一秒sentinel对其他sentinel以及主从中所有节点发送ping，心跳检测，作为正常或者宕机的判断依据</span><br><span class="line"></span><br><span class="line">### 17.4主客观下线  A---BC   DEF</span><br><span class="line"></span><br><span class="line">主观下线：第二句配置的30000，表示sentinel在没秒的定时任务发送ping以后，30秒都没有回复，那么sentinel就会认为该节点失败，因为是它一个人的看法，所以叫主观下线</span><br><span class="line"></span><br><span class="line">客观下线：在第一行配置的2，表示所有sentinel中有2个都认为该redis节点失败(主观下线)，那么就认为真的失败了，就可以继续后续操作了</span><br><span class="line"></span><br><span class="line">原理就是发送sentinel is-master-down-by-addr(地址)到_sentinel:hello，其它订阅的就可以获得信息</span><br><span class="line"></span><br><span class="line">### 17.5领导者选举(sentinel)</span><br><span class="line"></span><br><span class="line">目的:只需要一个sentinel完成故障转移就够了</span><br><span class="line">上面的sentinel is-master-down-by-addr(地址)还有一个作用，就是sentinel节点告诉其它sentinel我要当领导。</span><br><span class="line">规则:</span><br><span class="line">1.每个做主观下线的sentinel发送命令到其它sentinel告知master节点信息，以及要求其它sentinel设置发送该命令的sentinel成为领导</span><br><span class="line">​    2.每个sentinel只会同意收到第一个命令的发送者成为领导，投一票</span><br><span class="line">   3.如果有一个sentinel发现自己的票数超过sentinel集合半数并且超过了之前配置中的2，那么就会成为领导</span><br><span class="line">   4.该算法可能会有多个sentinel被选为领导，这个时候就会过一段时间从新选举</span><br><span class="line">具体算法就不去讨论了</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">### 17.6故障转移</span><br><span class="line"></span><br><span class="line">1.sentinel会在slave中选取一个来做为新的master</span><br><span class="line"></span><br><span class="line"> 选择依据:(了解)</span><br><span class="line"></span><br><span class="line">\1.   会查看slave中是否配置的优先级slave-priority，选择优先级高的(运维人员可以根据机器的配置高低去配置优先级)</span><br><span class="line"></span><br><span class="line">\2.    如果都没有配置那么就是相同的，会选择偏移量最大的slave节点(了解即可)</span><br><span class="line"></span><br><span class="line">\3.   选择runid最小的（就是redis运行的id，相当于一个标识符）</span><br><span class="line"></span><br><span class="line">2.选择了新的master，sentinel就会对剩下的slave发送命令，去复制新的master，复制规则，如图，下面就是表示一个一个来</span><br><span class="line"></span><br><span class="line">3.sentinel还会继续监控挂掉的master，如果master复活了，会命令master去从属于新选举的master</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">1-5  5选一个当领导 剩下4个  </span><br><span class="line">脑裂：</span><br></pre></td></tr></table></figure>

<h2 id="8-分片（Cluster）"><a href="#8-分片（Cluster）" class="headerlink" title="8.分片（Cluster）"></a>8.分片（Cluster）</h2><p>数据量太大时，主从无法解决，只能分片</p>
<p>主从：随便哪台都是所有的数据</p>
<p>分片 1000w  300w 300w 300w</p>
<p>redis–java–jedis</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">客户端分片：</span><br><span class="line">shardjedis  百度 springboot</span><br><span class="line">shardjedis  -----指定算法   指定虚拟节点个数(<span class="number">16</span> <span class="number">32</span>个数)</span><br><span class="line">	原理：hash算法：对key做hash</span><br><span class="line">	普通的hash算法</span><br><span class="line">	一致性hash算法：hash环---- 整个空间按顺时针方向组织。<span class="number">0</span>和<span class="number">2</span>^<span class="number">32</span>-<span class="number">1</span>在零点中方向重合。</span><br><span class="line">    	为了解决这种数据倾斜问题，一致性哈希算法引入了虚拟节点机制，即对每一个服务节点计算多个哈希，每个计算结果位置都放置一个此服务节点，称为虚拟节点。具体做法可以先确定每个物理节点关联的虚拟节点数量，然后在ip或者主机名后面增加编号。例如上面的情况，可以为每台服务器计算三个虚拟节点，于是可以分别计算 “Node A#<span class="number">1</span>”、“Node A#<span class="number">2</span>”、“Node A#<span class="number">3</span>”、“Node B#<span class="number">1</span>”、“Node B#<span class="number">2</span>”、“Node B#<span class="number">3</span>”的哈希值，于是形成六个虚拟节点：</span><br><span class="line">代理分片：</span><br><span class="line">	temproxy ----被codis替换了 <span class="number">1024</span>个槽  crc32算法 算出hash值%<span class="number">1024</span> 分槽 在codis</span><br><span class="line">	group1---<span class="number">0</span>-<span class="number">256</span></span><br><span class="line">	group2----<span class="number">256</span>---</span><br><span class="line">	.</span><br><span class="line">	.</span><br><span class="line">	groupn ----- x-<span class="number">1024</span></span><br><span class="line">	java 是跟codis交互</span><br><span class="line">集群：redisCluster----扩展重启redis</span><br><span class="line">	 <span class="number">3</span>套  副本</span><br><span class="line">	 <span class="number">16384</span></span><br><span class="line">	 <span class="number">1</span> <span class="number">0</span>-<span class="number">5000</span></span><br><span class="line">	 <span class="number">2</span> <span class="number">5000</span>-<span class="number">10000</span></span><br><span class="line">	 <span class="number">3</span> <span class="number">10000</span>-16xxx</span><br><span class="line">	 </span><br><span class="line">	 java直接链接的redis集群------随机一台，set a 到了第一台---move命令</span><br><span class="line">	 不好扩展：分槽在redis配置 在加一台，从新配置</span><br></pre></td></tr></table></figure>

<h2 id="9-redis雪崩"><a href="#9-redis雪崩" class="headerlink" title="9.redis雪崩"></a>9.redis雪崩</h2><p>1.redis大面积失效——key设置过期时间  随机一下  2级缓存 c1 c2   限流</p>
<p>2.缓存层宕机—-高可用高并发</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">缓存失效导致系统雪崩</span><br><span class="line"></span><br><span class="line">表示在一定时间内，缓存大面积失效(区分穿透)，导致全部请求命中到持久层(mysql)，导致系统崩溃。</span><br><span class="line"></span><br><span class="line">原因及解决方案:</span><br><span class="line"></span><br><span class="line">\1.   代码逻辑是先查询数据库再放入缓存中，没有查询导致缓存中没有数据，一瞬间来了大量请求全部打到数据库</span><br><span class="line"></span><br><span class="line">解决方案:缓存预热，在系统上线后，将可能使用到的数据，直接缓存到redis中</span><br><span class="line"></span><br><span class="line">\2.   由于redis过期时间设置得比较统一，导致缓存大面积过期</span><br><span class="line"></span><br><span class="line">这个就对缓存失效时间进行考虑，比如多少秒加个随机数等，避免大面积失效就可以了</span><br><span class="line"></span><br><span class="line">\3.   Redis宕机，服务器断电等</span><br><span class="line"></span><br><span class="line">增加redis高可用，集群、分片、读写分离、哨兵等(后面学习)</span><br><span class="line"></span><br><span class="line">当然上面根据情况预防了缓存雪崩，但是也不能百分之百的避免，所以还需要做如下事情来防止雪崩。</span><br><span class="line"></span><br><span class="line">\1.   在访问数据库的代码中，可以加入分布式互斥锁，保证在同一时间一个业务只有一个请求打到数据库</span><br><span class="line"></span><br><span class="line">\2.   使用双缓存策略(先请求缓存一，不命中，请求缓存二，不命中再去数据库)，降低发生概率 c1 c2</span><br><span class="line"></span><br><span class="line">\3.   使用组件完成后端，限流降级，例如histryx等技术(后面学习),sentinel</span><br><span class="line"></span><br><span class="line">\4.   当然，上线前多压力测试，是重中之重，比如双11，都要测试很多天的</span><br><span class="line"></span><br><span class="line"> 事后：rdb aof快速恢复</span><br></pre></td></tr></table></figure>

<h2 id="10-redis击穿"><a href="#10-redis击穿" class="headerlink" title="10.redis击穿"></a>10.redis击穿</h2><p>穿透：redis没有 mysql也没有</p>
<p>击穿：redis没有 mysql有</p>
<p>redis分布式锁双重检测</p>
<h2 id="11-双写一致性"><a href="#11-双写一致性" class="headerlink" title="11.双写一致性"></a>11.双写一致性</h2><p>延时双删</p>
<h2 id="12-发布订阅"><a href="#12-发布订阅" class="headerlink" title="12.发布订阅"></a>12.发布订阅</h2><p><strong>1、定义一个发布配置类PubConfig：指定频道。</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PubConfig</span> </span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 订阅发布的主题，理解为频道</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function">ChannelTopic <span class="title">topic</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ChannelTopic( <span class="string">&quot;LOGIN_TXX&quot;</span> );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>2、定义一个监听配置类：注入监听的频道</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SubConfig</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ChannelTopic topic;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function">MessageListenerAdapter <span class="title">messageListener</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> MessageListenerAdapter(<span class="keyword">new</span> MsgListener());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function">RedisMessageListenerContainer <span class="title">redisContainer</span><span class="params">(RedisConnectionFactory factory)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//redis监听容器 1.redis链接工厂 2.频道 (主题)</span></span><br><span class="line">        <span class="keyword">final</span> RedisMessageListenerContainer container = <span class="keyword">new</span> RedisMessageListenerContainer();</span><br><span class="line">        <span class="comment">//链接哪个redis,factory:读取到的就是配置的那一台redis</span></span><br><span class="line">        container.setConnectionFactory(factory);</span><br><span class="line">        <span class="comment">//可以装多个频道  可以订阅多个</span></span><br><span class="line">        container.addMessageListener(messageListener(), topic);</span><br><span class="line">        <span class="keyword">return</span> container;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>3、定义一个消息发布者</strong></p>
<p>注入其他类里，在需要发布消息时（登陆之后），调用sendMSg方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//消息发布者</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MsgPublisher</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate&lt;String, Object&gt; redisTemplate;</span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> ChannelTopic topic;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendMsg</span><span class="params">(Object object)</span></span>&#123;</span><br><span class="line">        <span class="comment">//topic.getTopic():指定消息发送到哪个频道  msg要发的消息</span></span><br><span class="line">        redisTemplate.convertAndSend(topic.getTopic(),object);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>4、定义一个消息监听器</strong></p>
<p>监听到消息时，调用踢下线的方法，</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 消息监听者</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MsgListener</span> <span class="keyword">implements</span> <span class="title">MessageListener</span> </span>&#123;</span><br><span class="line">    <span class="comment">// @Autowired</span></span><br><span class="line">    <span class="comment">// MyBeanUtil myBeanUtil;</span></span><br><span class="line">    <span class="comment">// private JacksonUtil jacksonUtil = myBeanUtil.getBean(JacksonUtil.class);;</span></span><br><span class="line">    <span class="comment">// @Autowired</span></span><br><span class="line">    <span class="comment">// public void setJacksonUtil(JacksonUtil a)&#123;</span></span><br><span class="line">    <span class="comment">//     jacksonUtil = a;</span></span><br><span class="line">    <span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onMessage</span><span class="params">(Message message, <span class="keyword">byte</span>[] bytes)</span> </span>&#123;</span><br><span class="line">            <span class="comment">// MapMessage message1 = (MapMessage) message;</span></span><br><span class="line">        <span class="comment">//收到消息后写代码逻辑</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Message received: &quot;</span> + message.toString());</span><br><span class="line">        String s = message.toString();</span><br><span class="line">        String substring = s.substring(<span class="number">1</span>, s.length() - <span class="number">1</span>);</span><br><span class="line">        Map&lt;String, String&gt; map1 = MessageUtil.mapMessageHandler(message);</span><br><span class="line">        <span class="comment">// Map&lt;String, String&gt; map = jacksonUtil.readValueMap(substring);</span></span><br><span class="line">        MyWebSocketHandler.kickOffline(map1.get(<span class="string">&quot;userId&quot;</span>), map1.get(<span class="string">&quot;address&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 踢下线，</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">kickOffline</span><span class="params">(String userId, String address)</span></span>&#123;</span><br><span class="line">    WebSocketSession webSocketSession = webSocketSessionMap.get(userId);</span><br><span class="line">    <span class="comment">// 当前userId 已经有会话连接，发消息通知当前连接，已经在其他地方登录</span></span><br><span class="line">    <span class="keyword">if</span>(webSocketSession != <span class="keyword">null</span>)&#123;</span><br><span class="line">        SystemResult fail = SystemResult.fail(<span class="string">&quot;你的账号在&quot;</span> + address + <span class="string">&quot;登录，你被迫下线!如果不是本人....&quot;</span>);</span><br><span class="line">        fail.setCode(SystemConstant.REPEAT_LOGIN);</span><br><span class="line"></span><br><span class="line">        String failStr = jacksonUtil.writeAsString(fail);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 给前端推送消息</span></span><br><span class="line">            webSocketSession.sendMessage(<span class="keyword">new</span> TextMessage(failStr)) ;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>说到项目上</p>
<h2 id="13-海量数据"><a href="#13-海量数据" class="headerlink" title="13.海量数据"></a>13.海量数据</h2><p>scan</p>
<p>复杂度虽然也是 O(n)，但是它是通过游标分步进行的，<strong>不会阻塞线程</strong>；</p>
<p>2、提供 limit 参数，可以控制每次返回结果的最大条数，limit 只是一个 hint，返回的结果可多可少;</p>
<p>3、同 keys 一样，它也提供模式匹配功能；</p>
<p>4、服务器不需要为游标保存状态，游标的唯一状态就是 scan 返回给客户端的游标整数;</p>
<p>5、返回的结果可能会有重复，需要客户端去重复，这点非常重要;</p>
<p>6、遍历的过程中如果有数据修改，改动后的数据能不能遍历到是不确定的;</p>
<p>7、单次返回的结果是空的并不意味着遍历结束，而要看返回的游标值是否为零</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SCAN cursor [MATCH pattern] [COUNT count]</span><br></pre></td></tr></table></figure>

<ul>
<li>cursor - 游标。</li>
<li>pattern - 匹配的模式。</li>
<li>count - 指定从数据集里返回多少元素，默认值为 10 </li>
</ul>
<p><strong>SCAN命令的返回值</strong> 是一个包含两个元素的数组， 第一个数组元素是用于进行下一次迭代的<strong>新游标</strong>， 而第二个数组元素则是一个数组， 这个数组中包含了所有被迭代的元素。<strong>游标返回0时</strong>，表示迭代结束。</p>
<h2 id="14-key过期处理"><a href="#14-key过期处理" class="headerlink" title="14.key过期处理"></a>14.key过期处理</h2><p>过期key处理：</p>
<ol>
<li><strong>惰性删除</strong> ：只会在<strong>取出 key 的时候才对数据进行过期检查</strong>。这样对 <strong>CPU</strong> 最友好，但是可能会造成太多过期 key 没有被删除。</li>
<li><strong>定期删除</strong> ： 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且，Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。默认1秒10次。</li>
</ol>
<p>定期删除对内存更加友好，惰性删除对 CPU 更加友好。两者各有千秋，所以 <strong>Redis 采用</strong>的是 <strong>定期删除+惰性/懒汉式删除</strong> 。</p>
<p>还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里，然后就 Out of memory 了。</p>
<p>怎么解决这个问题呢？答案就是：<strong>Redis 内存淘汰机制。</strong>lru、lfu、random、ttl，两种数据范围：全空间、设置过期的key的集合。</p>
<ol>
<li><strong>volatile-lru（least recently used）</strong>：从已设置过期时间的数据集（server.db[i].expires）中挑选最近最少使用的数据淘汰</li>
<li><strong>volatile-ttl</strong>：从已设置过期时间的数据集（server.db[i].expires）中挑选将要过期的数据淘汰</li>
<li><strong>volatile-random</strong>：从已设置过期时间的数据集（server.db[i].expires）中任意选择数据淘汰</li>
<li><strong>allkeys-lru（least recently used）</strong>：当内存不足以容纳新写入数据时，在键空间中，移除最近最少使用的 key（这个是最常用的）</li>
<li><strong>allkeys-random</strong>：从数据集（server.db[i].dict）中任意选择数据淘汰</li>
<li><strong>no-eviction</strong>：禁止驱逐数据，也就是说当内存不足以容纳新写入数据时，新写入操作会报错。这个应该没人使用吧！</li>
</ol>
<p>4.0 版本后增加以下两种：</p>
<ol>
<li><strong>volatile-lfu（least frequently used）</strong>：从已设置过期时间的数据集（server.db[i].expires）中挑选最不经常使用的数据淘汰</li>
<li><strong>allkeys-lfu（least frequently used）</strong>：当内存不足以容纳新写入数据时，在键空间中，移除最不经常使用的 key</li>
</ol>
<h2 id="15-分布式锁"><a href="#15-分布式锁" class="headerlink" title="15.分布式锁"></a>15.分布式锁</h2><p>Redisson分布式锁：<strong>不适用于主从</strong></p>
<p>对某个redis master实例，写入了myLock这种锁key的value，此时会异步复制给对应的master slave实例。但是这个过程中一旦发生redis master宕机，主备切换，redis slave变为了redis master。</p>
<p>接着就会导致，客户端2来尝试加锁的时候，在新的redis master上完成了加锁，而客户端1也以为自己成功加了锁。</p>
<p>此时就会导致多个客户端对一个分布式锁完成了加锁。</p>
<p>这时系统在业务语义上一定会出现问题，<strong>导致各种脏数据的产生</strong>。</p>
<p>所以这个就是redis cluster，或者是redis master-slave架构的<strong>主从异步复制</strong>导致的redis分布式锁的最大缺陷：在redis master实例宕机的时候，可能导致多个客户端同时完成加锁。</p>
<blockquote>
<p>分布式锁：互斥、防止死锁、性能、重入</p>
<p><strong>互斥：</strong>在分布式高并发的条件下，我们最需要保证，同一时刻只能有一个线程获得锁，这是最基本的一点。</p>
<p><strong>防止死锁：</strong>在分布式高并发的条件下，比如有个线程获得锁的同时，还没有来得及去释放锁，就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁，造成死锁。</p>
<p>所以分布式非常有必要设置锁的<code>有效时间</code>，确保系统出现故障后，在一定时间内能够主动去释放锁，避免造成死锁的情况。</p>
<p><strong>性能：</strong>1、<code>锁的颗粒度要尽量小</code>。比如扣库存，只锁商品ID；2、<code>锁的范围尽量要小</code></p>
</blockquote>
<h3 id="Redisson分布式锁"><a href="#Redisson分布式锁" class="headerlink" title="Redisson分布式锁"></a>Redisson分布式锁</h3><h4 id="1、加锁机制"><a href="#1、加锁机制" class="headerlink" title="1、加锁机制"></a>1、加锁机制</h4><p><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/135864820">https://zhuanlan.zhihu.com/p/135864820</a></p>
<p>线程去获取锁，获取成功：执行lua脚本，保存数据到redis数据库。</p>
<figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;exists&#x27;</span>, KEYS[<span class="number">1</span>]) == <span class="number">0</span>) <span class="keyword">then</span> <span class="string">&quot; +</span></span><br><span class="line"><span class="string">   &quot;</span>redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>); <span class="string">&quot; +</span></span><br><span class="line"><span class="string">   &quot;</span>redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]); <span class="string">&quot; +</span></span><br><span class="line"><span class="string">   &quot;</span><span class="keyword">return</span> <span class="literal">nil</span>; <span class="string">&quot; +</span></span><br><span class="line"><span class="string">   &quot;</span><span class="keyword">end</span>; <span class="string">&quot; +</span></span><br><span class="line"><span class="string">&quot;</span><span class="keyword">if</span> (redis.call(<span class="string">&#x27;hexists&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>]) == <span class="number">1</span>) <span class="keyword">then</span> <span class="string">&quot; +</span></span><br><span class="line"><span class="string">    &quot;</span>redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>); <span class="string">&quot; +</span></span><br><span class="line"><span class="string">    &quot;</span>redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]); <span class="string">&quot; +</span></span><br><span class="line"><span class="string">    &quot;</span><span class="keyword">return</span> <span class="literal">nil</span>; <span class="string">&quot; +</span></span><br><span class="line"><span class="string">    &quot;</span><span class="keyword">end</span>; <span class="string">&quot; +</span></span><br><span class="line"><span class="string">&quot;</span><span class="keyword">return</span> redis.call(<span class="string">&#x27;pttl&#x27;</span>, KEYS[<span class="number">1</span>]);<span class="string">&quot;</span></span><br></pre></td></tr></table></figure>

<p>线程去获取锁，获取失败: 一直通过while循环尝试获取锁，获取成功后，执行lua脚本，保存数据到redis数据库。</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">SuJin</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://sujiesujie12456789.gitee.io/2021/12/18/redis/">https://sujiesujie12456789.gitee.io/2021/12/18/redis/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://sujiesujie12456789.gitee.io" target="_blank">SuJin博客</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"></div><div class="post_share"><div class="social-share" data-image="https://picsum.photos/id/862/5451/3634" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/social-share.js/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2022/03/30/MyBatis/"><img class="prev-cover" src="https://picsum.photos/id/862/5451/3634" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">MyBatis学习</div></div></a></div><div class="next-post pull-right"><a href="/2021/12/18/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E7%AF%87%E5%8D%9A%E5%AE%A2/"><img class="next-cover" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">MySQL学习笔记</div></div></a></div></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="http://picsum.photos/id/859/1919/1919" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">SuJin</div><div class="author-info__description">SuJin的博客，欢迎访问</div></div><div class="card-info-data is-center"><div class="card-info-data-item"><a href="/archives/"><div class="headline">文章</div><div class="length-num">11</div></a></div><div class="card-info-data-item"><a href="/tags/"><div class="headline">标签</div><div class="length-num">0</div></a></div><div class="card-info-data-item"><a href="/categories/"><div class="headline">分类</div><div class="length-num">0</div></a></div></div><a class="button--animated" id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://gitee.com/sujin1125" target="_blank" title="Gitee"><i class="fab fa-github"></i></a><a class="social-icon" href="mailto:sujin_a@163.com" target="_blank" title="Email"><i class="fas fa-envelope"></i></a><a class="social-icon" href="https://sujin1125.gitee.io/" target="_blank" title="博客"><i class="fab fa-algolia"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn card-announcement-animation"></i><span>公告</span></div><div class="announcement_content">This is my Blog</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BC%98%E5%8A%BF%EF%BC%9A"><span class="toc-number">1.</span> <span class="toc-text">优势：</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#1-redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84"><span class="toc-number">2.</span> <span class="toc-text">1.redis底层数据结构</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-Hash%E5%86%B2%E7%AA%81%E6%80%8E%E4%B9%88%E5%8A%9E"><span class="toc-number">3.</span> <span class="toc-text">&#x3D;&#x3D;2.Hash冲突怎么办&#x3D;&#x3D;</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-redis-GEO"><span class="toc-number">4.</span> <span class="toc-text">2.redis GEO</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-redis%E4%BA%8B%E5%8A%A1-lua-%E7%AE%A1%E9%81%93"><span class="toc-number">5.</span> <span class="toc-text">3.redis事务&#x2F;lua&#x2F;管道</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-redis%E7%A9%BF%E9%80%8F"><span class="toc-number">6.</span> <span class="toc-text">4.redis穿透</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-redis%E6%8C%81%E4%B9%85%E5%8C%96"><span class="toc-number">7.</span> <span class="toc-text">5.redis持久化</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#6-redis%E8%AF%BB%E5%86%99%E5%88%86%E7%A6%BB"><span class="toc-number">8.</span> <span class="toc-text">6.redis读写分离</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-redis%E5%93%A8%E5%85%B5%E6%9C%BA%E5%88%B6-%E2%98%85%E2%98%85%E2%98%85%E2%98%85%E2%98%85-sentinel"><span class="toc-number">9.</span> <span class="toc-text">7.redis哨兵机制(★★★★★)sentinel</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#8-%E5%88%86%E7%89%87%EF%BC%88Cluster%EF%BC%89"><span class="toc-number">10.</span> <span class="toc-text">8.分片（Cluster）</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#9-redis%E9%9B%AA%E5%B4%A9"><span class="toc-number">11.</span> <span class="toc-text">9.redis雪崩</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#10-redis%E5%87%BB%E7%A9%BF"><span class="toc-number">12.</span> <span class="toc-text">10.redis击穿</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#11-%E5%8F%8C%E5%86%99%E4%B8%80%E8%87%B4%E6%80%A7"><span class="toc-number">13.</span> <span class="toc-text">11.双写一致性</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#12-%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85"><span class="toc-number">14.</span> <span class="toc-text">12.发布订阅</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#13-%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE"><span class="toc-number">15.</span> <span class="toc-text">13.海量数据</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#14-key%E8%BF%87%E6%9C%9F%E5%A4%84%E7%90%86"><span class="toc-number">16.</span> <span class="toc-text">14.key过期处理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#15-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81"><span class="toc-number">17.</span> <span class="toc-text">15.分布式锁</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Redisson%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81"><span class="toc-number">17.1.</span> <span class="toc-text">Redisson分布式锁</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1%E3%80%81%E5%8A%A0%E9%94%81%E6%9C%BA%E5%88%B6"><span class="toc-number">17.1.1.</span> <span class="toc-text">1、加锁机制</span></a></li></ol></li></ol></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/2022/04/15/java%E7%AC%94%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/" title="“java笔试题总结”"><img src="https://picsum.photos/id/862/5451/3634" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="“java笔试题总结”"/></a><div class="content"><a class="title" href="/2022/04/15/java%E7%AC%94%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/" title="“java笔试题总结”">“java笔试题总结”</a><time datetime="2022-04-15T07:53:41.000Z" title="发表于 2022-04-15 15:53:41">2022-04-15</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/03/30/Nginx/" title="Nginx"><img src="https://picsum.photos/id/862/5451/3634" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Nginx"/></a><div class="content"><a class="title" href="/2022/03/30/Nginx/" title="Nginx">Nginx</a><time datetime="2022-03-30T14:01:07.000Z" title="发表于 2022-03-30 22:01:07">2022-03-30</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/03/30/SpringMVC%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/" title="SpringMVC学习笔记"><img src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="SpringMVC学习笔记"/></a><div class="content"><a class="title" href="/2022/03/30/SpringMVC%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/" title="SpringMVC学习笔记">SpringMVC学习笔记</a><time datetime="2022-03-30T13:35:27.000Z" title="发表于 2022-03-30 21:35:27">2022-03-30</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2022/03/30/MyBatis/" title="MyBatis学习"><img src="https://picsum.photos/id/862/5451/3634" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="MyBatis学习"/></a><div class="content"><a class="title" href="/2022/03/30/MyBatis/" title="MyBatis学习">MyBatis学习</a><time datetime="2022-03-30T13:08:22.000Z" title="发表于 2022-03-30 21:08:22">2022-03-30</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2021/12/18/redis/" title="redis笔记"><img src="https://picsum.photos/id/862/5451/3634" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="redis笔记"/></a><div class="content"><a class="title" href="/2021/12/18/redis/" title="redis笔记">redis笔记</a><time datetime="2021-12-18T12:47:07.000Z" title="发表于 2021-12-18 20:47:07">2021-12-18</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2022 By SuJin</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>